﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BusinessCache
{
    //实现分布式锁
    public interface ILock
    {
        Task<bool> TryLockAsync(string lockKey, int expiresSenconds = 10, int waitTimeSenconds = 2);
        bool TryLock(string lockKey, int expiresSenconds = 10, int waitTimeSenconds = 2);
        bool TryDelLock(string lockKey);
        Task<bool> TryDelLockAsync(string lockKey);
    }

    public class LockByRedis : ILock
    {
        private RedisCache _redis;
        //默认过期时间10s
        private readonly int _defaultExpires = 10;
        //等待时重试时间间隔：毫秒，默认60毫秒
        private readonly int _retryInterval = 60;
        //1970年
        private readonly DateTime _time1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);

        public LockByRedis(int dbNum = 0)
        {
            _redis = new RedisCache(dbNum);
        }

        //public void Init(int dbNum = 0)
        //{
        //    _redis = new RedisCache(dbNum);
        //}

        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <returns></returns>
        private long CurrentTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - _time1970;
            return Convert.ToInt64(ts.TotalSeconds * 1000);
        }

        /// <summary>
        /// 获取锁
        /// </summary>
        /// <param name="key">上锁的key</param>
        /// <param name="expiresMS">过期时间：秒，默认10秒。传入的expiresSenconds如果小于等于0，为了防止死锁：那么expiresSenconds也会调整为10</param>
        /// <param name="waitTimeSenconds">等待时间，默认10s</param>
        /// <returns></returns>
        public async Task<bool> TryLockAsync(string lockKey, int expiresSenconds = 10, int waitTimeSenconds = 60)
        {

            if (expiresSenconds <= 0)
            {
                expiresSenconds = _defaultExpires;
            }

            if (waitTimeSenconds <= 0)
            {
                //return await new CacheByRedis().set(lockKey, 1, expiresSenconds, RedisExistence.Nx);
                await _redis.StringSetNotExistsAsync(lockKey, "1", TimeSpan.FromSeconds(expiresSenconds), StackExchange.Redis.When.NotExists);

            }

            long now = CurrentTimeStamp();
            long waitEndTime = now + waitTimeSenconds * 1000;
            long leftTime = 0;
            bool result = false;

            while (!result && now <= waitEndTime)
            {
                result = await _redis.StringSetNotExistsAsync(lockKey, "1", TimeSpan.FromSeconds(expiresSenconds),StackExchange.Redis.When.NotExists);

                if (!result)
                {
                    leftTime = waitEndTime - now;

                    if (leftTime >= _retryInterval)
                    {
                        await Task.Delay(_retryInterval);
                    }
                    else
                    {
                        await Task.Delay((int)leftTime);
                    }

                    now = CurrentTimeStamp();
                }
            }

            return result;
        }

        public bool TryLock(string lockKey,int expiresSenconds = 10, int waitTimeSenconds = 1)
        {
            if (expiresSenconds <= 0)
            {
                expiresSenconds = _defaultExpires;
            }

            if (waitTimeSenconds <= 0)
            {
                
                return _redis.StringSetNotExists(lockKey, "1", TimeSpan.FromSeconds(expiresSenconds), StackExchange.Redis.When.NotExists);
            }

            long now = CurrentTimeStamp();
            long waitEndTime = now + waitTimeSenconds * 1000;
            bool result = false;

            while (!result && now <= waitEndTime)
            {
                result = _redis.StringSetNotExists(lockKey, "1", TimeSpan.FromSeconds(expiresSenconds), StackExchange.Redis.When.NotExists);

                if (!result)
                {
                    Thread.Sleep(_retryInterval);
                    now = CurrentTimeStamp();
                }
            }

            return result;
        }

      

        public bool TryDelLock(string lockKey)
        {
            return _redis.KeyDelete(lockKey);
        }

        public Task<bool> TryDelLockAsync(string lockKey)
        {
            return _redis.KeyDeleteAsync(lockKey);
        }

    }
}
